home *** CD-ROM | disk | FTP | other *** search
/ Scene 96 / Scene 96 International Edition (Zyklop Software) (Disc 2) (1997).iso / misc / coding / vgacodng / part06.txt < prev    next >
Text File  |  1996-08-07  |  16KB  |  368 lines

  1.  
  2.                              VGA-Kurs - Part #6
  3.  
  4. 'T.C.P.s Beginner's Guide To VGA Coding', Part VI ist da!
  5. Heute wollen wir uns über ein Thema unterhalten, mit dem viele Anfänger
  6. Schwierigkeiten haben. Sie wollen ein Programm schreiben. Sie wissen auch, wie
  7. sie im Grafikmodus ein paar Punkte setzen können, aber das hilft ihnen nicht
  8. weiter, denn sie wollen in ihr Programm ein Bild einbauen. Entweder haben sie
  9. es selbst gezeichnet oder zeichnen lassen oder was auch immer. Auf jeden Fall
  10. wollen sie es unbedingt in ihrem Programm haben, und zwar möglichst
  11. professionell, also nicht durch ein Hintertürchen, indem sie einen externen
  12. Viewer wie PicView dazupacken und an der entsprechenden Stelle im Programm mit
  13. der EXEC-Prozedur aufrufen.
  14. Also, wie mache ich das jetzt, ein Bild anzuzeigen?
  15. Tja, erst mal brauchen wir ein geeignetes Format, das wir leicht einlesen und
  16. anzeigen können. Das simpelste Format von allen, auch wenn es mehr oder weniger
  17. inoffiziell ist, ist das RAW-Format. Der Aufbau dieses Formats ist in einem
  18. Satz erklärt: Alle Daten des Bildes werden in einem Rutsch vom
  19. Bildschirmspeicher in eine Datei geschrieben. Das Anzeigen eines solchen
  20. Bildes ist ebenso einfach: Alle Daten werden aus der Datei nacheinander zurück
  21. in den Bildschirmspeicher geschrieben. Dies sieht nun in Pascal so aus:
  22.  
  23. procedure SaveRAW(Name:string);
  24. var f : file;
  25. begin
  26.   assign(f,Name);
  27.   rewrite(f,1);                        { Datei erstellen }
  28.   blockwrite(f,mem[$A000:0],64000);
  29.   { Ab Adresse $A000:0 64000 Byte lesen und in die Datei schreiben }
  30.   close(f);                            { Datei wieder schließen }
  31. end;
  32.  
  33. procedure LoadRAW(Name:string);
  34. var f : file;
  35. begin
  36.   assign(f,Name);
  37.   reset(f,1);                          { Datei für Lesezugriff vorbereiten }
  38.   blockread(f,mem[$A000:0],64000);
  39.   { 64000 Byte aus Datei lesen und in den Bildschirmspeicher schreiben }
  40.   close(f);
  41. end;
  42.  
  43. Da hier allerdings nur die Bildinformationen gespeichert werden, und nicht die
  44. notwendige Palette, müssen wir diese auch noch sichern:
  45.  
  46. procedure GetPal(col:byte;var r,g,b:byte);
  47. begin
  48.   port[$3C7] := Col;
  49.   r := port[$3C9];
  50.   g := port[$3C9];
  51.   b := port[$3C9];
  52. end;
  53.  
  54. procedure SetPal(col:byte;r,g,b:byte);
  55. begin
  56.    port[$3C8] := Col;
  57.    port[$3C9] := r;
  58.    port[$3C9] := g;
  59.    port[$3C9] := b;
  60. end;
  61.  
  62. procedure SavePal(Name:string);
  63. var f   : file;
  64.     n   : byte;
  65.     RGB : array[1..3] of byte;
  66. begin
  67.   assign(f,Name);
  68.   rewrite(f,1);
  69.   for n := 0 to 255 do begin
  70.     getpal(n,RGB[1],RGB[2],RGB[3]);
  71.     blockwrite(f,RGB,3);
  72.   end;
  73.   close(f);
  74. end;
  75.  
  76. procedure LoadPal(Name:string);
  77. var f   : file;
  78.     n   : byte;
  79.     RGB : array[1..3] of byte;
  80. begin
  81.   assign(f,Name);
  82.   reset(f,1);
  83.   for n := 0 to 255 do begin
  84.     blockread(f,RGB,3);
  85.     setpal(n,RGB[1],RGB[2],RGB[3]);
  86.   end;
  87.   close(f);
  88. end;
  89.  
  90. Man kann die Prozeduren auch kombinieren, indem man die Palettendaten an die
  91. RAW-Datei anhängt. So macht es das (unkomprimierte) TGA-Format auch. Allerdings
  92. speichert es die Palettendaten in der Reihenfolge Blau, Grün, Rot ab. Außerdem
  93. ist jeder Farbwert mit 4 multipliziert.
  94. Jetzt stehen wir vor einem weiteren Problem: Da das RAW-Format kaum verbreitet
  95. ist, gibt es kein Konvertierprogramm (außer Paintshop Pro 3.0, allerdings
  96. speichert es die RAWs aus unerfindlichen Gründen falschrum ab), mit dem man
  97. seine Bilder in RAWs konvertieren kann. Also müssen wir uns selbst darum
  98. kümmern. Beim folgenden Konvertierer habe ich mich für PCX als Eingabeformat
  99. entschieden. Das GIF-Format ist zwar wesentlich mehr verbreitet, darf aber
  100. aus allseits bekannten Gründen hier nicht zur Anwendung kommen.
  101.  
  102. program PCX2RAW;
  103. uses crt;
  104. type TPCXHeader = record               { Header der PCX-Datei }
  105.                     Manuf,Version,Encode,BitsPerPixel : byte;
  106.                     X1,Y1,X2,Y2,Xres,Yres : integer;
  107.                     Palette          : array[0..47] of byte;
  108.                     VideoMode,Planes : byte;
  109.                     BytesPerLine     : integer;
  110.                     Reserved         : array[0..59] of byte;
  111.                   end;
  112.      PPCXPic = ^TPCXPic;
  113.      TPCXPic = record
  114.                  Header  : TPCXHeader;            { Der Header }
  115.                  Palette : array[0..767] of byte; { Die Palette }
  116.                  Pixels  : pointer;               { Das Bild }
  117.                end;
  118. var PCX_        : TPCXPic;
  119.     I           : integer;
  120.     palf,rawf   : file;
  121.     PCX,PAL,RAW : string;
  122.  
  123. procedure LoadPCX(FileName:string;var PCX:TPCXPic); { Lädt PCX-Datei }
  124. var F               : file;
  125.     Buf             : array[0..1024] of byte;
  126.     BufPtr,Off,Size : word;
  127.     Code,Count      : byte;
  128. begin
  129.   assign(F,FileName);
  130.   reset(F,1);
  131.   blockread(F,PCX.Header,sizeof(PCX.Header)); { Header einlesen }
  132.   with PCX.Header do                          { und auswerten }
  133.     if (Manuf <> 10) or (Version <> 5) or (Encode <> 1) or
  134.        (BitsPerPixel <> 8) or (Planes <> 1) or
  135.        (BytesPerLine > 320) or (Y2 - Y1 > 199) then begin
  136.       PCX.Pixels := nil;               { Bild kann nicht dargestellt werden }
  137.       exit;
  138.     end;
  139.   Size := PCX.Header.BytesPerLine * succ(PCX.Header.Y2 - PCX.Header.Y1);
  140.   { Bildgröße ermitteln }
  141.   getmem(PCX.Pixels,Size);
  142.   if PCX.Pixels = nil then exit;
  143.   BufPtr := sizeof(Buf);
  144.   Off := 0;                            { Offset in der PCX-Datei }
  145.   while Off < Size do begin
  146.     if BufPtr >= sizeof(Buf) then begin
  147.       blockread(F,Buf,sizeof(Buf));    { Daten lesen }
  148.       BufPtr := 0;
  149.     end;
  150.     Code := Buf[BufPtr];
  151.     inc(BufPtr);
  152.     if Code shr 6 = 3 then begin       { Dekomprimierung }
  153.       Count := Code and 63;
  154.       if BufPtr >= sizeof(Buf) then begin
  155.         blockread(F,Buf,sizeof(Buf));
  156.         BufPtr := 0;
  157.       end;
  158.       Code := Buf[BufPtr];
  159.       inc(BufPtr);
  160.       fillchar(mem[Seg(PCX.Pixels^):ofs(PCX.Pixels^)+Off],Count,Code);
  161.       inc(Off,Count);
  162.     end
  163.     else begin
  164.       mem[seg(PCX.Pixels^):ofs(PCX.Pixels^)+Off] := Code;
  165.       inc(Off);
  166.     end;
  167.   end;
  168.   if BufPtr >= sizeof(Buf) then begin
  169.     blockread(F,Buf,sizeof(Buf));
  170.     BufPtr := 0;
  171.   end;
  172.   Code := Buf[BufPtr];
  173.   inc(BufPtr);
  174.   if Code = 12 then begin
  175.     for Off := 0 to 767 do begin
  176.       if BufPtr >= sizeof(Buf) then begin
  177.         blockread(F,Buf,767-Off);
  178.         BufPtr := 0;
  179.       end;
  180.       PCX.Palette[Off] := Buf[BufPtr];
  181.       inc(BufPtr);
  182.     end;
  183.   end;
  184.   close(F);
  185. end;
  186. procedure FreePCX(var PCX:TPCXPic);
  187. begin
  188.   if PCX.Pixels <> nil then
  189.     freemem(PCX.Pixels,PCX.Header.BytesPerLine*succ(PCX.Header.Y2-PCX.Header.Y1));
  190. end;
  191. begin
  192.   if paramcount <> 2 then halt;
  193.   PCX := paramstr(1);                  { Name der PCX-Datei }
  194.   RAW := paramstr(2);                  { Name der RAW-Datei }
  195.   PAL := RAW;                          { Name der PAL-Datei }
  196.   delete(PAL,pos('.',PAL),4);          { eventuelle RAW-Endung entfernen }
  197.   PAL := PAL + '.pal';                 { Endung '.PAL' anhängen }
  198.   LoadPCX(PCX,PCX_);                   { PCX-Datei laden }
  199.   if PCX_.Pixels = nil then begin      { Fehler beim Laden }
  200.     writeln(#13#10'Error reading PCX file: ',PCX);
  201.     halt;
  202.   end;
  203.   asm mov ax,13h; int 10h end;         { Modus 13h setzen }
  204.   port[$3C8] := 0;                     { Palette setzen }
  205.   for I := 0 to 767 do begin
  206.     PCX_.Palette[I] := PCX_.Palette[I] shr 2;
  207.     Port[$3C9] := PCX_.Palette[I];
  208.   end;
  209.   with PCX_ do                         { Bild darstellen }
  210.     for I := Header.Y1 to Header.Y2 do
  211.       Move(mem[seg(PCX_.Pixels^):ofs(PCX_.Pixels^)+I*Header.BytesPerLine],
  212.            mem[$A000:320*I],Header.X2 - Header.X1 + 1);
  213.   assign(rawf,RAW);                    { Dateien vorbereiten }
  214.   rewrite(rawf,1);
  215.   assign(palf,PAL);
  216.   rewrite(palf,1);
  217.   with PCX_ do                         { RAW-File schreiben }
  218.     for I := Header.Y1 to Header.Y2 do
  219.       blockwrite(rawf,mem[$A000:320*I],Header.X2 - Header.X1 + 1);
  220.   blockwrite(palf,PCX_.Palette,768);   { PAL-File schreiben }
  221.   readkey;
  222.   close(rawf);
  223.   close(palf);
  224.   textmode(3);
  225. end.
  226.  
  227. Ein wirklich langer Quelltext, aber leider ein Muß.
  228. Aufgerufen wird das Programm folgendermaßen: PCX2RAW [PCXFile] [RAWFile].
  229. Es folgt nun die Aufschlüsselung des PCX-Formats, wahrscheinlich nur für
  230. etwas fortgeschrittenere Leser interessant, aber trotzdem:
  231. Am Anfang der PCX-Datei steht ein 128 Byte langer Header, in dem sämtliche
  232. Informationen über die Eigenschaften des Bildes stehen. Hier die Offsets:
  233.  
  234. Offset   Bytes   Bedeutung
  235. --------------------------
  236.    0       1     Identifikationsbyte. Enthält den Wert 10, wenn PCX-Datei.
  237.    1       1     Versionsnummer. Werte:
  238.                  0 : Version 2.5
  239.                  2 : Version 2.8 mit Palettendaten
  240.                  3 : Version 2.8 ohne Palettendaten
  241.                  5 : Version 3.0
  242.    2       1     Komprimierungsinfo. Werte:
  243.                  0 : Bild nicht gepackt
  244.                  1 : Bild gepackt
  245.    3       1     Bits pro Pixel (8 für 256 Farben)
  246.    4       2     Linke obere X-Koord
  247.    6       2     Linke obere Y-Koord
  248.    8       2     Rechte untere X-Koord
  249.   10       2     Rechte untere Y-Koord
  250.   12       2     X Auflösung
  251.   14       2     Y Auflösung
  252.  16-65    59     Palette für 16-Farben-Bilder
  253.   64       1     Videomodus
  254.   65       1     Anzahl der Farbebenen (Planes)
  255.   66       2     Bytes pro Zeile
  256.   68       2     Paletteninfo. Werte:
  257.                  1 : Mono/Farbe
  258.                  2 : Graustufen
  259. 70-127    57     Bisher unbenutzt
  260.  
  261. Jetzt zum Kompressionsverfahren. Es funktioniert folgendermaßen:
  262. Sind in einem Datenbyte die zwei obersten Bits gesetzt, ist es ein Zählerbyte,
  263. d.h. die unteren 6 Bytes bilden einen Wert von 1 bis 63, der bestimmt, wie
  264. oft sich das darauffolgende Byte wiederholt. Ist dagegen nur eins bzw. gar
  265. keins der beiden Bits gesetzt, kann das Byte so wie es ist in den
  266. Bildschirmspeicher eingelesen werden.
  267. Das Verfahren nennt sich übrigens Lauflängenkodierung, zu Englisch Run Length
  268. Encoding (RLE).
  269. Was aber, wenn man unkomprimiertes Byte hat, in dem die obersten Bits gesetzt
  270. sind, z.B. 255? Dann muß man wohl oder übel ein Zählerbyte erzeugen, das
  271. anzeigt, daß einmal der Wert 255 eingelesen werden muß, womit ein Byte
  272. verschwendet wäre. Ein Beispiel:
  273.  
  274. Unkomprimiert: 00h 00h 00h 00h 01h 05h FFh
  275. Komprimiert:   C4h 00h 01h 05h C1h FFh
  276.  
  277. Hier werden 2 Bytes gespart, weil wir die 4 '00h' zu 'C4h 00h' zusammenfassen
  278. können. Allerdings geht auch ein Byte wieder verloren, da bei 'FFh' die oberen
  279. beiden Bits gesetzt und wir 'C1h FFh' schreiben müssen.
  280. Das maximale Kompressionsverhältnis beträgt also 1 zu 32 (es können 64 Byte zu
  281. 2 Byte zusammengefaßt werden). Allerdings kann es im ungünstigsten Fall auch
  282. zu einer Vergrößerung des Umfangs der Daten kommen.
  283.  
  284. Da wir jetzt aber das PCX-Format einlesen können, wozu brauchen wir dann noch
  285. das RAW-Format? Nun, es liegt wohl auf der Hand, daß ein einziges Blockread
  286. sehr viel einfacher und auch schneller ist, als der gesamte obige Code für das
  287. Einlesen einer PCX-Grafik. Außerdem ist es ein wenig professioneller, wenn man
  288. für seine Programm ein Format benutzt, das nicht jeder mit einem Viewer
  289. einsehen oder die Bilder klauen kann. Trotzdem hat das RAW-Format einen
  290. gewichtigen Nachteil: Es ist zu groß.
  291. Dem kann man abhelfen, indem man sich einen Kompressionsalgorithmus schreibt,
  292. ähnlich wie oben beschrieben. Das ist nicht so schwer wie es sich anhört. Ich
  293. zum Beispiel benutze für das DFI-Format (Diabolic Force Image) einen
  294. Algorithmus, der in weniger als 30 Zeilen Platz findet. Trotzdem packt er recht
  295. gut.
  296. Ein eigenes Format ist also nicht von Nachteil. Da das Kompressionsverfahren
  297. von PCX nicht geschützt ist, könnten wir es dafür verwenden, aber wir wollen
  298. doch mal sehen, ob wir es nicht noch verbessern können.
  299. Die Lauflängenkodierung, wie sie oben beschrieben ist, stellt schon mal einen
  300. guten Anfang dar. Jedoch sollte man darüber nachdenken, statt der oberen
  301. 2 Bits die obersten 3 Bits zur Markierung eines Zählerbytes zu benutzen.
  302. Die 3 Bits könnten dann folgendes bedeuten:
  303.  
  304. 000xxxxx : X Null-Bytes (kommen sehr haüfig vor)
  305. 001xxxxx : X mal das folgende Byte
  306. 010xxxxx : Die nächsten X Bytes unverändert in den Speicher laden
  307. 011xxxxx : X mal die folgenden 2 Bytes
  308. 100xxxxx : Die 5 Restbits & die 8 Bits des nächsten Bytes als Zähler für
  309.            das übernächste Byte (13 Bit = 1-8191)
  310. usw.
  311.  
  312. Für den Rest könnt ihr euch selbst was passendes einfallen lassen.
  313. Das obige Verfahren dürfte ca. 5-10%, bei weniger komplexen Bildern bis zu
  314. 30% mehr Kompression einbringen.
  315. Ein anderes komplexes Verfahren, das ebenso viele Freunde wie Feinde haben
  316. dürfte, ist das JPEG-Format. Es komprimiert die Bilddaten wie kein anderes
  317. Format, allerdings mit dem Nachteil, das Bildinformationen verloren gehen.
  318. Außerdem ist die Kompression bzw. Dekompression so aufwendig und langsam, daß
  319. es sich kaum für Spiele oder andere Programme eignen dürfte. Wenn ihr also
  320. nicht gerade einen Viewer schreiben wollt, könnt ihr darauf verzichten.
  321. Ein weiterer Bereich, in dem die Kompressionsmethoden immer mehr verbessert
  322. werden, ist der der digitalen Videos. Hier konnte sich das MPEG-Verfahren, das
  323. mit JPEG verwandt ist, einigermaßen durchsetzen, jedoch sind hierfür teure
  324. Zusatzkarten erforderlich, wenn man nicht gerade einen ultraschnellen PC hat.
  325. Für Animationen gibt es außerdem das bekannte FLI-Format, bzw. dessen
  326. Weiterentwicklung, das FLC. Hier kommt eine Technik zum Einsatz, bei der nur
  327. die Daten gespeichert werden, die zur vorigen Frame (Einzelbild) unidentisch
  328. sind. Die erste Frame wird also komplett gespeichert (ebenfalls mit einer Art
  329. Lauflängenkodierung). Danach folgen die nächsten Frames, wobei jede einen
  330. eigenen Header besitzt, in dem steht, wie groß und von welchem Typ sie ist.
  331. Falls sich die Palette der Frame geändert hat, enthält der darauffolgende
  332. Datenteil als erstes eine gepackte Palette. Anschließend folgen die Daten,
  333. allerdings nur die, die unterschiedlich zur vorigen Frame sind.
  334. Sehr gute und ausfürliche Beschreibungen von Grafik- und Animations-, aber auch
  335. Sound-Formaten u.a. finden sich in der PC Games Programmer's Encyclopedia 1.0.
  336.  
  337. Wer sich also jetzt vorgenommen hat, sein eigenes Bild- oder sogar Video-Format
  338. zu schreiben, dem sei gesagt: Es führt kein Weg am Assembler vorbei.
  339. Außerdem ist ein Algorithmus nie ganz ausgereizt, es gibt immer Wege, ihn zu
  340. verbessern.
  341. Ich hoffe, die Ausführungen in diesem Teil haben euch geholfen, und euch nicht
  342. zu sehr verwirrt.
  343. Was im nächsten Teil kommen wird, weiß ich noch nicht genau, laßt euch einfach
  344. überraschen.
  345.  
  346.  
  347.  
  348.  
  349.  
  350. [ This text copyright (c) 1995-96 Johannes Spohr. All rights reserved. ]
  351. [ Distributed exclusively through PC-Heimwerker, Verlag Thomas Eberle. ]
  352. [                                                                      ]
  353. [ No  part   of  this   document  may  be   reproduced,   transmitted, ]
  354. [ transcribed,  stored in a  retrieval system,  or translated into any ]
  355. [ human or computer language, in any form or by any means; electronic, ]
  356. [ mechanical,  magnetic,  optical,   chemical,  manual  or  otherwise, ]
  357. [ without the expressed written permission of the author.              ]
  358. [                                                                      ]
  359. [ The information  contained in this text  is believed  to be correct. ]
  360. [ The text is subject to change  without notice and does not represent ]
  361. [ a commitment on the part of the author.                              ]
  362. [ The author does not make a  warranty of any kind with regard to this ]
  363. [ material, including,  but not limited to,  the implied warranties of ]
  364. [ merchantability  and fitness  for a particular  purpose.  The author ]
  365. [ shall not be liable for errors contained herein or for incidental or ]
  366. [ consequential damages in connection with the furnishing, performance ]
  367. [ or use of this material.                                             ]
  368.